package com.czb.seckill.controller;

import cn.hutool.json.JSONUtil;
import com.czb.seckill.config.AccessLimit;
import com.czb.seckill.pojo.SecKillOrder;
import com.czb.seckill.pojo.SeckillMessage;
import com.czb.seckill.pojo.User;
import com.czb.seckill.rabbitmq.MQSenderMessage;
import com.czb.seckill.service.GoodsService;
import com.czb.seckill.service.OrderService;
import com.czb.seckill.service.SecKillOrderService;
import com.czb.seckill.vo.GoodsVo;
import com.czb.seckill.vo.RespBean;
import com.czb.seckill.vo.RespBeanEnum;
import com.ramostear.captcha.HappyCaptcha;
import com.ramostear.captcha.common.Fonts;
import com.ramostear.captcha.support.CaptchaStyle;
import com.ramostear.captcha.support.CaptchaType;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author: 陈正彬
 */
@Controller
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {
    @Resource
    private GoodsService goodsService;
    @Resource
    private SecKillOrderService secKillOrderService;
    @Resource
    private OrderService orderService;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private MQSenderMessage mqSenderMessage;
    @Resource
    private RedisScript<Long> redisScript;

    //定义map，记录秒杀商品是否还有库存
    private HashMap<Long, Boolean> entryStockMap = new HashMap<>();

//    //方法，处理用户抢购请求
//    //先完成一个v1.0版本，后面在高并发情况下在优化
//    @RequestMapping("/doSeckill")
//    public String doSeckill(Model model, User user,Long goodsId){
//
//        System.out.println("-----V1.0------");
//
//        if (user == null){
//            //没有登入
//            return "login";
//        }
//        //将user放入model，下一个模板可以使用
//        model.addAttribute("user",user);
//        //获取到goodsVO
//        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
//        //判断库存
//        if (goodsVo.getStockCount() < 1){
//            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK);
//            return "secKillFail";
//        }
//        //判断是否复购,当前用户id和购买商品id是否已经在商品秒杀订单表存在
//        SecKillOrder secKillOrder = secKillOrderService.getOne(new QueryWrapper<SecKillOrder>().
//                eq("user_id", user.getId()).eq("goods_id", goodsId));
//        if (secKillOrder != null) {
//            model.addAttribute("errmsg",RespBeanEnum.REPEAT_ERROR);
//            return "secKillFail";
//        }
//        //抢购
//        Order order = orderService.seckill(user, goodsVo);
//        if (order == null){
//            model.addAttribute("errmsg",RespBeanEnum.ENTRY_STOCK);
//            return "secKillFail";
//        }
//        //进入订单页
//        model.addAttribute("order",order);
//        model.addAttribute("goods",goodsVo);
//
//        return "orderDetail";
//    }

//    //方法，处理用户抢购请求,V2.0
//    @RequestMapping("/doSeckill")
//    public String doSeckill(Model model, User user, Long goodsId) {
//
//
//        if (user == null) {
//            //没有登入
//            return "login";
//        }
//        //将user放入model，下一个模板可以使用
//        model.addAttribute("user", user);
//        //获取到goodsVO
//        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
//        //判断库存
//        if (goodsVo.getStockCount() < 1) {
//            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
//            return "secKillFail";
//        }
//        //判断是否复购,到redis获取对应的秒杀订单
//        SecKillOrder o = (SecKillOrder) redisTemplate.opsForValue().
//                get("order:" + user.getId() + ":" + goodsVo.getId());
//        if (o != null) {
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
//            return "secKillFail";
//        }
//
//        //抢购
//        Order order = orderService.seckill(user, goodsVo);
//        if (order == null) {
//            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
//            return "secKillFail";
//        }
//        //进入订单页
//        model.addAttribute("order", order);
//        model.addAttribute("goods", goodsVo);
//
//        return "orderDetail";
//    }

    //方法: 处理用户抢购请求/秒杀
    //说明：先完成一个V3.0版本，加入消息队列,实现秒杀的异步请求
    @RequestMapping("/doSeckill")
    public String doSeckill(Model model, User user, Long goodsId) {


        if (user == null) {//用户没有登录
            return "login";
        }
        //将user放入到model, 下一个模板可以使用
        model.addAttribute("user", user);

        //获取到goodsVo
        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);

        //判断库存
        if (goodsVo.getStockCount() < 1) {//没有库存
            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
            return "secKillFail";//错误页面
        }


        //判断用户是否复购-直接到Redis中,获取对应的秒杀订单,如果有,则说明已经抢购了
        SecKillOrder o = (SecKillOrder) redisTemplate.opsForValue()
                .get("order:" + user.getId() + ":" + goodsVo.getId());
        if (null != o) { //说明该用户已经抢购了该商品
            model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
            return "secKillFail";//错误页面
        }

        //对map进行判断[内存标记],如果商品在map已经标记为没有库存，则直接返回，无需进行Redis预减
        if (entryStockMap.get(goodsId)) {
            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
            return "secKillFail";//错误页面
        }

        //库存预减, 如果在Redis中预减库存，发现秒杀商品已经没有了，就直接返回
        //从而减少去执行 orderService.seckill() 请求,防止线程堆积, 优化秒杀/高并发
        // decrement()是具有原子性[!!]
        //Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
        //if (decrement < 0) {//说明这个商品已经没有库存
        //
        //    //说明当前秒杀的商品，已经没有库存
        //    entryStockMap.put(goodsId, true);
        //
        //    //恢复库存为0
        //    redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
        //    model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
        //    return "secKillFail";//错误页面
        //}

        //=====使用Redis分布式锁====
        //1. 对于当前项目而言，使用redisTemplate.opsForValue().decrement() 就可以控制抢购,因为该方法具有原子性和隔离性
        //2. 考虑到如果有比较多的操作，需要保证隔离性，也就是说，不是简单的-1,而是有多个操作
        //   这样就需要扩大隔离性的范围，部分操作还需要原子性

        //1 获取锁，setnx
        //得到一个 uuid 值，作为锁的值
        String uuid = UUID.randomUUID().toString();
        Boolean lock =
                redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        //2 获取锁成功
        if (lock) {

            //准备释放锁lua脚本
//            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
//            redisScript.setScriptText(script);
//            redisScript.setResultType(Long.class);

            Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
            if (decrement < 0) {//说明这个商品已经没有库存
                //说明当前秒杀的商品，已经没有库存
                entryStockMap.put(goodsId, true);
                //恢复库存为0
                redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
                //释放锁. redis+lua脚本
                redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
                model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
                return "secKillFail";//错误页面
            }

            //释放分布式锁
            redisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);

        } else {
            //3 获取锁失败,返回个信息[本次抢购失败，请再次抢购...]
            model.addAttribute("errmsg", RespBeanEnum.SEC_KILL_RETRY.getMessage());
            return "secKillFail";//错误页面
        }

        //抢购,向消息队列发送秒杀请求,实现了秒杀异步请求
        //发送秒杀消息后，立即快速返回结果[临时结果]
        //客户端可以通过轮询，获取到最终结果
        //创建SeckillMessage
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        mqSenderMessage.sendSeckillMessage(JSONUtil.toJsonStr(seckillMessage));
        model.addAttribute("errmsg", "排队中...");
        return "secKillFail";

    }

    //方法，处理用户抢购请求,V4.0,redis预减库存,内存标记优化,加入消息队列实现秒杀异步请求
    //加入 秒杀安全校验,直接返回RespBean
//    @RequestMapping("/{path}/doSeckill")
//    @ResponseBody
//    public RespBean doSeckill(@PathVariable String path, Model model, User user, Long goodsId) {
//
//
//        if (user == null) {
//            //没有登入
////            return "login";
//            return RespBean.error(RespBeanEnum.SESSION_ERROR);
//        }
//
//        //校验用户携带的path是否正确
//        Boolean b = orderService.checkPath(user, goodsId, path);
//        if (!b) {
//            return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
//        }
//
//        //将user放入model，下一个模板可以使用
////        model.addAttribute("user", user);
//        //获取到goodsVO
//        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
//        //判断库存
//        if (goodsVo.getStockCount() < 1) {
////            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
////            return "secKillFail";
//            return RespBean.error(RespBeanEnum.ENTRY_STOCK);
//        }
//        //判断是否复购,到redis获取对应的秒杀订单
//        SecKillOrder o = (SecKillOrder) redisTemplate.opsForValue().
//                get("order:" + user.getId() + ":" + goodsVo.getId());
//        if (o != null) {
////            model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
////            return "secKillFail";
//            return RespBean.error(RespBeanEnum.REPEAT_ERROR);
//        }
//
//        //对map判断(内存标记),如果商品在map已经标记为没有库存，就直接返回，不要进行redis预减库存
//        if (entryStockMap.get(goodsId)) {
////            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
////            return "secKillFail";
//            return RespBean.error(RespBeanEnum.ENTRY_STOCK);
//        }
//
//
//        //redis库存预减,减少执行orderService.seckill，优化秒杀
//        Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
//        if (decrement < 0) {
//            //内存标记为没有库存
//            entryStockMap.put(goodsId, true);
//
//            //恢复库存到0,否则在redis中会出现负数
//            redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
////            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
////            return "secKillFail";
//            return RespBean.error(RespBeanEnum.ENTRY_STOCK);
//        }
//
//
////        //抢购
////        Order order = orderService.seckill(user, goodsVo);
////        if (order == null) {
////            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
////            return "secKillFail";
////        }
////        //进入订单页
////        model.addAttribute("order", order);
////        model.addAttribute("goods", goodsVo);
////
////        return "orderDetail";
//
//        //抢购向消息队列发送秒杀请求，实现异步秒杀请求
//        //发送秒杀消息后快速返回 临时结果,客户端通过轮询获得最终结果
//        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
//        mqSenderMessage.sendSeckillMessage(JSONUtil.toJsonStr(seckillMessage));
////        model.addAttribute("errmsg","排队中...");
////        return "secKillFail";
//        return RespBean.error(RespBeanEnum.SEC_KILL_WAIT);
//
//    }

    //方法，获取秒杀路径
    @RequestMapping("/path")
    @ResponseBody
    //自定义注解完成对用户的限流防刷
    @AccessLimit(second = 5,maxCount = 5,needLogin = true)
    public RespBean getPath(User user, Long goodsId,String captcha,
                            HttpServletRequest request,HttpServletResponse response) {

        if (user == null || goodsId < 0 ) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        if (!StringUtils.hasText(captcha)){
            return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
        }

        //加入redis计数器，完成对用户限流防刷
        //5秒内访问次数超过5次
//        String requestURI = request.getRequestURI();
//        ValueOperations valueOperations = redisTemplate.opsForValue();
//        String key = requestURI + ":" + user.getId();
//        Integer count = (Integer) valueOperations.get(key);
//        if (count == null){
//            //说明还没有key,  初始化
//            valueOperations.set(key,1,5,TimeUnit.SECONDS);
//        } else if (count < 5) { //正常访问
//            redisTemplate.opsForValue().increment(key);
//        } else {//5秒内访问超过5次
//            return RespBean.error(RespBeanEnum.ACCESS_LIMIT_REACHED);
//        }


        //校验用户的验证码是否正确
        Boolean checkCaptcha = orderService.checkCaptcha(user, goodsId, captcha);
        if (!checkCaptcha){
            return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
        }

        String path = orderService.createPath(user, goodsId);
        return RespBean.success(path);

    }

    //生成验证码
    @RequestMapping("/captcha")
    public void happyCaptcha(HttpServletRequest request, HttpServletResponse response,
                             User user, Long goodsId) {

        //生成验证码，默认保存到session中，key默认是 happy—captcha
        HappyCaptcha.require(request, response)
                .style(CaptchaStyle.ANIM) //设置展现样式为动画
                .type(CaptchaType.NUMBER) //设置验证码内容为数字
                .length(6) //设置字符长度为6
                .width(220) //设置动画宽度为220
                .height(80) //设置动画高度为80
                .font(Fonts.getInstance().zhFont()) //设置汉字的字体
                .build().finish(); //生成并输出验证码

        //保存到redis
        redisTemplate.opsForValue().set("captcha:" + user.getId() + goodsId,
                (String)request.getSession().getAttribute("happy-captcha"),60, TimeUnit.SECONDS);
    }

    //该方法在类的所有属性都初始化后，自动执行
    //这里将所有秒杀商品的库存量加载到redis
    @Override
    public void afterPropertiesSet() throws Exception {

        //查询所有的秒杀商品
        List<GoodsVo> list = goodsService.findGoodsVo();

        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        //遍历list，将秒杀商品的库存量放入redis
        list.forEach(goodsVo -> {
            redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(),
                    goodsVo.getStockCount());
            //初始化map
            //goodsId : false 表示有库存
            entryStockMap.put(goodsVo.getId(), false);

        });


    }
}
